#pragma warning(disable : 4995)

#include "../DXUT/dxstdafx.h"
#include "../core/crt.h"
#include "../core/crtutil.h"
#include "Shadow.h"


extern TCRTScene g_scene;

wchar_t* ShadowTechName[] = 
{
	L"No Shadow",
	L"Standard Shadow Map",
	//L"Focused SSM",
	L"Perspective Shadow Map",
	//L"Light Space PSM",
	//L"Optimized Light Space PSM",
	//L"Optimized Light Space PSM + PCA",
	0
};

wchar_t* ShadowResName[] = 
{
	L"2048x2048",
	L"1024x1024",
	L"512x512",
	L"256x256",
	0
};

UINT ShadowMapRes[] = 
{
	2048, 
	1024,
	512,
	256,
	0
};

wchar_t* ShadowSampleName[] = 
{
	L"sample 1x1",
	L"sample 2x2",
	L"sample 3x3",
	L"sample 4x4",
	L"sample 5x5",
	L"sample 6x6",
	L"sample 7x7",
	L"sample 8x8",
	0
};

UINT ShadowSampleNum[] = 
{
	1,
	4, 
	9, 
	16, 
	25,
	36,
	49,
	64, 
	0
};




D3DXVECTOR3 GetUpDirection(D3DXVECTOR3& ViewDirection)
{
	D3DXVECTOR3 UpDirection(0, 1, 0);

	if (fabs(D3DXVec3Dot(&UpDirection, &ViewDirection)) > 0.99f)
		UpDirection = D3DXVECTOR3(1, 0, 0);
	UpDirection -= ViewDirection * D3DXVec3Dot(&ViewDirection, &UpDirection);
	D3DXVec3Normalize( &UpDirection, &UpDirection );

	return UpDirection;
}



ShadowSolution::ShadowSolution(IDirect3DDevice9* deviceIn)
{
	D3DDevice = deviceIn;

	TransformDirty = true;
	BufferDirty = true;

	Tech = ST_SSM;
	Resolution = SR_1024;
	Sample = SSD_1x1;

	LightDirection = D3DXVECTOR3(0, -1, 0);
	CameraPosition = D3DXVECTOR3(0, 0, 0);
	SceneMin = D3DXVECTOR3(-1, -1, -1);
	SceneMax = D3DXVECTOR3(1, 1, 1);

	DepthBuffer		= 0;
	DepthTexture	= 0;

	ToCreateDebugInfo = false;
}

ShadowSolution::~ShadowSolution()
{
	SAFE_RELEASE(DepthBuffer);
	SAFE_RELEASE(DepthTexture);
}

// Guassian Distribution : f(x) = 1/(d*sqrt(2*PI)) * e^(-(x-miu)^2 / (2*d^2))
//	Where the constant scaling factor could be ignored
//
// http://mathworld.wolfram.com/GaussianFunction.html
//
void UnifyWeight(D3DXVECTOR4* Sample, int SampleNum, float ClampThresh, int Resolution/*, float rotRad*/)
{
	// We first solve 	e^(-ClampThresh^2 / (2*d^2)) = 0.05 to get d

	float d = sqrtf(-ClampThresh * ClampThresh * 0.5f / (logf(0.05f)));
	float sum = 0;

	for (int i = 0; i < SampleNum; i++)
	{
		float distant2 = Sample[i].x * Sample[i].x + Sample[i].y * Sample[i].y;
		Sample[i].z = expf(-distant2 * 0.5f / d / d);
		sum += Sample[i].z;
	}

	sum = 1.0f / sum;
	float invRes = 1.0f / Resolution;
	//float sinRad = sinf(rotRad);
	//float cosRad = cosf(rotRad);
	for (int i = 0; i < SampleNum; i++)
	{
		// Rotation and scale
		float x = Sample[i].x * invRes;
		float y = Sample[i].y * invRes;
	
		//Sample[i].x = x * sinRad - y * cosRad;
		//Sample[i].y = x * cosRad + y * sinRad;

		Sample[i].x = x;
		Sample[i].y = y;
		
		Sample[i].z *= sum;
	}
}

int offset_to_offset[SSD_NUM] = 
{
	0,
	1,
	1 + 4,
	1 + 4 + 9,
	1 + 4 + 9 + 16,
	1 + 4 + 9 + 16 + 25,
	1 + 4 + 9 + 16 + 25 + 36,
	1 + 4 + 9 + 16 + 25 + 36 + 49,
};

D3DXVECTOR4* ShadowSolution::GetSampleOffset() const 
{
	static D3DXVECTOR4 offset[1 + 4 + 9 + 16 + 25 + 36 + 49 + 64];

	static bool FirstRun = true;

	if (FirstRun)
	{
		FirstRun = false;

		for (int i = 0; i < SSD_NUM; i++)
		{
			int size = i + 1;
			float start = -0.5f * i;

			D3DXVECTOR4* section = offset + offset_to_offset[i];

			for (int m = 0; m < size; m++)
			{
				for (int n = 0; n < size; n++)
				{
					section[m * size + n] = D3DXVECTOR4(start + m, start + n, 0, 0);
				}
			}
			UnifyWeight( section, size * size, (i == 0) ? 1.0f : (fabs(start) * 1.5f), ShadowMapRes[Resolution] );
		}
	}

	return offset + offset_to_offset[Sample];
}

void ShadowSolution::SetTech(ShadowTech techIn)
{
	if (techIn != Tech)
	{
		Tech = techIn;
		TransformDirty = true;
	}
}

void ShadowSolution::SetResolution(ShadowRes resIn)
{
	if (resIn != Resolution)
	{
		Resolution	= resIn;
		BufferDirty = true;
	}
}

void ShadowSolution::SetLightDirection(const D3DXVECTOR3& lightDir)
{
	if (LightDirection != lightDir)
	{
		LightDirection = lightDir;
		TransformDirty = true;
	}
}

void ShadowSolution::SetCameraPosition(const D3DXVECTOR3& cameraPos)
{
	if (CameraPosition != cameraPos)
	{
		CameraPosition = cameraPos;
		if (Tech != ST_NO && Tech != ST_SSM)
		{
			TransformDirty = true;
		}
	}
}

void ShadowSolution::SetCameraDirection(const D3DXVECTOR3& cameraDirection)
{
	if (CameraDirection != cameraDirection)
	{
		CameraDirection = cameraDirection;
		if (Tech != ST_NO && Tech != ST_SSM)
		{
			TransformDirty = true;
		}
	}
}

void ShadowSolution::SetCameraUp(const D3DXVECTOR3& cameraUp)
{
	if (CameraUp != cameraUp)
	{
		CameraUp = cameraUp;
		if (Tech != ST_NO && Tech != ST_SSM)
		{
			TransformDirty = true;
		}
	}
}

void ShadowSolution::SetCameraParam(float nearClip, float farClip, float aspect, float fovy)
{
	NearClip = nearClip;
	FarClip = farClip;
	Aspect = aspect;
	FieldOfViewY = fovy;

	TransformDirty = true;
}

void ShadowSolution::SetSceneMin(const D3DXVECTOR3& sceneMin)
{
	SceneMin = sceneMin;
	TransformDirty = true;
}

void ShadowSolution::SetSceneMax(const D3DXVECTOR3& sceneMax)
{
	SceneMax = sceneMax;
	TransformDirty = true;
}

HRESULT ShadowSolution::OnResetDevice(IDirect3DDevice9* pD3DDevice)
{
	HRESULT hr;

	D3DDevice = pD3DDevice;

	V_RETURN( D3DDevice->CreateDepthStencilSurface(
		ShadowMapRes[Resolution], 
		ShadowMapRes[Resolution], 
		D3DFMT_D24S8, 
		D3DMULTISAMPLE_NONE, 0,
		TRUE,
		&DepthBuffer,
		NULL) );

	V_RETURN( D3DDevice->CreateTexture(
		ShadowMapRes[Resolution],
		ShadowMapRes[Resolution],
		1,
		D3DUSAGE_RENDERTARGET,
		D3DFMT_R32F,
		D3DPOOL_DEFAULT,
		&DepthTexture,
		NULL) );

	return S_OK;
}

HRESULT ShadowSolution::OnLostDevice()
{
	SAFE_RELEASE(DepthBuffer);
	SAFE_RELEASE(DepthTexture);

	return S_OK;
}

HRESULT ShadowSolution::PreRenderDepth()
{
	HRESULT hr;

	if (Tech == ST_NO)
		return S_OK;

	V_RETURN( D3DDevice->GetRenderTarget( 0, &StandardColorTarget ) );
	V_RETURN( D3DDevice->GetDepthStencilSurface( &StandardDepthTarget ) );

	if (BufferDirty)
		UpdateBuffer();

	if (TransformDirty)
		UpdateTransform();

	V( D3DDevice->GetViewport( &StandardViewport ) );

	IDirect3DSurface9* pSurface;
	V( DepthTexture->GetSurfaceLevel( 0, &pSurface ) );
	V( D3DDevice->SetRenderTarget( 0, pSurface ) );
	V( D3DDevice->SetDepthStencilSurface( DepthBuffer ) );
	pSurface->Release();
	
	D3DVIEWPORT9 NewViewport;
	NewViewport.X = 0;
	NewViewport.Y = 0;
	NewViewport.Width = ShadowMapRes[Resolution];
	NewViewport.Height = ShadowMapRes[Resolution];
	NewViewport.MinZ = 0.0f;
	NewViewport.MaxZ = 1.0f;

	V( D3DDevice->SetViewport( &NewViewport ));

	V( D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, InvertDepthFunc ? D3DCOLOR_ARGB(1, 1, 1, 1) : D3DCOLOR_ARGB(0, 0, 0, 0), InvertDepthFunc ? 0.0f : 1.0f, 0) );
	V( D3DDevice->SetRenderState( D3DRS_ZFUNC, InvertDepthFunc ? D3DCMP_GREATEREQUAL : D3DCMP_LESSEQUAL ) );
	V( D3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ) );

	return hr;
}

HRESULT ShadowSolution::PostRenderDepth()
{
	HRESULT	hr;

	if (Tech == ST_NO)
		return S_OK;

	V( D3DDevice->SetRenderTarget( 0, StandardColorTarget ) );
	V( D3DDevice->SetDepthStencilSurface( StandardDepthTarget ) );
	StandardDepthTarget->Release();
	StandardColorTarget->Release();	

	V( D3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ) );
	V( D3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ) );
	V( D3DDevice->SetViewport( &StandardViewport ));

	return hr;
}

HRESULT ShadowSolution::DumpDepthTexture(TCHAR* filename)
{
	HRESULT hr;

	IDirect3DSurface9* pSurface;
	V( DepthTexture->GetSurfaceLevel( 0, &pSurface ) );
	V( D3DXSaveSurfaceToFile( filename, D3DXIFF_PNG, pSurface, NULL, NULL ) );
	pSurface->Release();

	return hr;
}

HRESULT ShadowSolution::UpdateBuffer()
{
	HRESULT	hr;

	SAFE_RELEASE(DepthBuffer);
	V_RETURN( D3DDevice->CreateDepthStencilSurface(
		ShadowMapRes[Resolution], 
		ShadowMapRes[Resolution], 
		D3DFMT_D24S8, 
		D3DMULTISAMPLE_NONE, 0,
		TRUE,
		&DepthBuffer,
		NULL) );

	SAFE_RELEASE(DepthTexture);
	V_RETURN( D3DDevice->CreateTexture(
		ShadowMapRes[Resolution],
		ShadowMapRes[Resolution],
		1,
		D3DUSAGE_RENDERTARGET,
		D3DFMT_R32F,
		D3DPOOL_DEFAULT,
		&DepthTexture,
		NULL) );

	// The matrix transformation from light perspective space to depth texture space
	float A = 1.0f * (ShadowMapRes[Resolution] - 1) / ShadowMapRes[Resolution];
	float B = 0.5f / ShadowMapRes[Resolution];

	D3DXMATRIXA16	ScaleMat, TranslationMat;
	D3DXMatrixScaling( &ScaleMat, 0.5f * A, -0.5f * A, 1 );
	D3DXMatrixTranslation( &TranslationMat, 0.5f * A + B, 0.5f * A + B, 0 );
	CubeToTexture = ScaleMat * TranslationMat;

	BufferDirty = false;
	return hr;
}

void ShadowSolution::UpdateTransform()
{	
	//double Func0 = CRTGetCurrentTime();
	switch (Tech)
	{
	case ST_SSM : 
		UpdateTransform_SSM();
		break;

	case ST_PSM : 
		UpdateTransform_PSM();
		break;

	//case ST_FSSM : 
	//	UpdateTransform_FSSM();
	//	break;

	//case ST_LSPSM :
	//	UpdateTransform_LSPSM();
	//	break;

	//case ST_LSPSM_OPT :
	//	UpdateTransform_LSPSM_OPT();
	//	break;

	//case ST_LSPSM_OPT_PCA :
	//	UpdateTransform_LSPSM_OPT_PCA();
	//	break;
	}

	TransformDirty = false;
	ToCreateDebugInfo = false;

	//double Func1 = CRTGetCurrentTime();
	//DXUTOutputDebugStringW(L"UpdateTransform: %.2fms\n", Func1 - Func0);
}

// Directional light
void ShadowSolution::UpdateTransform_SSM()
{
	// Put eye at the scene center
	D3DXMATRIXA16	LightView, LightProj;

	D3DXVECTOR3 sceneSize = SceneMax - SceneMin;
	float Radius = D3DXVec3Length(&sceneSize) * 0.5f;

	D3DXVECTOR3		EyePosition = (SceneMin + SceneMax) * 0.5f;
	D3DXVECTOR3		UpDirection = GetUpDirection(LightDirection);

	D3DXVECTOR3	EyeTarget = EyePosition + LightDirection;
	D3DXMatrixLookAtLH( &LightView, &EyePosition, &EyeTarget, &UpDirection );
	D3DXMatrixOrthoLH( &LightProj, 2.0f * Radius, 2.0f * Radius, -Radius, Radius);

	ShadowGen = LightView * LightProj;
	ShadowUse = ShadowGen * CubeToTexture;

	InvertDepthFunc = false;
}

// Keep the position, direction, up, fov, aspect parameter fixed, shrink the [nearclip, farclip] range.
Frustum ShadowSolution::GetTightEyeFrustum()
{
	float Near = FLT_MAX;
	float Far  = -FLT_MAX;
	float FovX = -FLT_MAX;
	float FovY = -FLT_MAX;

	D3DXMATRIXA16 EyeView;
	D3DXVECTOR3 CameraTarget = CameraPosition + CameraDirection;
	D3DXMatrixLookAtLH(&EyeView, &CameraPosition, &CameraTarget, &CameraUp);

	for (vector<TCRTMesh*>::iterator it = g_scene.meshes.begin(); it != g_scene.meshes.end(); it++)
	{
		AABB& aabb = (*it)->aabb;

		for (int i = 0; i < 8; i++)
		{
			D3DXVECTOR3 _V = D3DXVECTOR3(&aabb.Vertex(i));
			D3DXVECTOR3 TransformedV;

			D3DXVec3TransformCoord( &TransformedV, &_V, &EyeView );

			Near	= min(Near, TransformedV.z);
			Far		= max(Far, TransformedV.z);
			FovX	= max(FovX, fabs(TransformedV.x / TransformedV.z));
			FovY	= max(FovY, fabs(TransformedV.y / TransformedV.z));
		}
	}

	Near	= max(NearClip, Near);
	Far		= min(FarClip, Far);
	FovX	= atanf(FovX) * 2.0f;
	FovY	= atanf(FovY) * 2.0f;
	FovY	= min(FovY, FieldOfViewY);
	FovX	= min(FovX, 2.0f * atanf(tanf(FieldOfViewY * 0.5f) * Aspect));

	VEC3 vec3CameraPosition = VEC3(CameraPosition);
	VEC3 vec3CameraDirection = VEC3(CameraDirection);
	VEC3 vec3CameraUp = VEC3(CameraUp);
	return Frustum(vec3CameraPosition, vec3CameraDirection, vec3CameraUp, Near, Far, FovX, FovY);
}

Frustum ShadowSolution::GetEyeFrustum()
{
	VEC3 vec3CameraPosition = VEC3(CameraPosition);
	VEC3 vec3CameraDirection = VEC3(CameraDirection);
	VEC3 vec3CameraUp = VEC3(CameraUp);

	float FieldOfViewX = 2.0f * atanf(Aspect * tanf(FieldOfViewY * 0.5f));

	return Frustum(vec3CameraPosition, vec3CameraDirection, vec3CameraUp, NearClip, FarClip, FieldOfViewX, FieldOfViewY);
}

//void ShadowSolution::UpdateTransform_FSSM()
//{
//	InvertDepthFunc = false;
//
//	// Find the tight frustum from eye
//	Frustum TightFrustum = GetTightEyeFrustum();
//
//	// We put light source in the frustum center
//	D3DXVECTOR3		LightPosition = CameraPosition + CameraDirection * 0.5f * (TightFrustum.nearclip + TightFrustum.farclip);
//
//	// Light up direction
//	D3DXVECTOR3		UpDirection = GetUpDirection( LightDirection );
//
//	D3DXMATRIXA16	LightView, LightProj;
//	D3DXVECTOR3 LightTarget = LightPosition + LightDirection;
//	D3DXMatrixLookAtLH( &LightView, &LightPosition, &LightTarget, &UpDirection );
//
//	AABB aabb;
//
//	for (int i = 0; i < 8; i++)
//	{
//		D3DXVECTOR3	TransformedV;
//		D3DXVECTOR3 FrustumVertex(&TightFrustum.vert[i]);
//		D3DXVec3TransformCoord( &TransformedV, &FrustumVertex, &LightView );
//
//		VEC3 vec3TransformedVertex(TransformedV);
//		aabb += vec3TransformedVertex;
//	}
//
//	D3DXVECTOR3 NewMinV, NewMaxV;
//	// Also take scene bounding box into consideration - only the Z value
//	D3DXVECTOR3 MinV(&g_scene.aabb.minV);
//	D3DXVECTOR3 MaxV(&g_scene.aabb.maxV);
//	TransformBoundingBox( LightView, MinV, MaxV, NewMinV, NewMaxV );
//
//	VEC3 vec3NewMinV(0, 0, NewMinV.z);
//	VEC3 vec3NewMaxV(0, 0, NewMaxV.z);
//	aabb += vec3NewMinV;
//	aabb += vec3NewMaxV;
//
//	VEC3 SafeBoundary = aabb.GetSize() * 0.2f;	// 10%
//	D3DXMatrixOrthoOffCenterLH( &LightProj, 
//		aabb.minV.x - SafeBoundary.x, 
//		aabb.maxV.x + SafeBoundary.x, 
//		aabb.minV.y - SafeBoundary.y, 
//		aabb.maxV.y + SafeBoundary.y, 
//		aabb.minV.z - SafeBoundary.z, 
//		aabb.maxV.z + SafeBoundary.z );
//
//	ShadowGen = LightView * LightProj;
//	ShadowUse = ShadowGen * CubeToTexture;	
//}
//
void ShadowSolution::UpdateTransform_PSM()
{
	// Find the tight frustum from eye
	Frustum TightFrustum = GetTightEyeFrustum(); 

	// Move camera back, an improvement to original PSM
	float shift = (FarOverNear * TightFrustum.nearclip - TightFrustum.farclip) / (FarOverNear - 1.0f);
	D3DXVECTOR3 VirtualCameraPosition = CameraPosition + CameraDirection * shift;

	VEC3 vec3VirtualCameraPosition(VirtualCameraPosition);
	VEC3 vec3CameraDirection(CameraDirection);
	VEC3 vec3CameraUp(CameraUp);
	Frustum VirtualCameraFrustum( vec3VirtualCameraPosition, vec3CameraDirection, vec3CameraUp, TightFrustum );

	D3DXMATRIXA16 CameraView, CameraProj;
	D3DXVECTOR3 VirtualCameraTarget = VirtualCameraPosition + CameraDirection;
	D3DXMatrixLookAtLH( &CameraView, &VirtualCameraPosition, &VirtualCameraTarget, &CameraUp );
	D3DXMatrixPerspectiveFovLH( &CameraProj, VirtualCameraFrustum.fovy, VirtualCameraFrustum.aspect, 
		VirtualCameraFrustum.nearclip, VirtualCameraFrustum.farclip );

	if (ToCreateDebugInfo)
	{
		VEC3 vec3VirtualCameraPosition(VirtualCameraPosition);
		VEC3 vec3CameraDirection(CameraDirection);
		VEC3 vec3CameraUp(CameraUp);

		Frustum Fru(vec3VirtualCameraPosition, 
			vec3CameraDirection,
			vec3CameraUp,
			VirtualCameraFrustum.nearclip, 
			VirtualCameraFrustum.farclip, 
			VirtualCameraFrustum.fovx, 
			VirtualCameraFrustum.fovy);

		DebugFrustum.push_back(Fru);
	}

	D3DXMATRIXA16	World2CameraPPS = CameraView * CameraProj;

	// Transform the light direction from world space to post perspective space
	D3DXVECTOR4	LD(LightDirection, 0);
	D3DXVECTOR4	PPSLight4;	
	D3DXMATRIXA16 PPSLightView, PPSLightProj;

	D3DXVec4Transform( &PPSLight4, &LD, &World2CameraPPS );
	D3DXVECTOR4 _PPSLight4 = PPSLight4 / PPSLight4.w;
	D3DXVECTOR3 _A(-1,-1,0);
	D3DXVECTOR3 _B(1,1,1);
	if (fabs(PPSLight4.w) < 0.01f || D3DXVec4Length(&_PPSLight4) > 100.f)
	{
		// If the PPSLight is far away, we'll use an ortho light source instead
		D3DXVECTOR3 OrthoLightDirection(PPSLight4.x,PPSLight4.y,PPSLight4.z);
		D3DXVECTOR3 OrthoLightPosition(0,0,0);		

		D3DXVec3Normalize( &OrthoLightDirection, &OrthoLightDirection );	
		D3DXVECTOR3 OrthoLightUp = GetUpDirection( OrthoLightDirection );
		D3DXVECTOR3 OrthoLightTarget = OrthoLightDirection + OrthoLightPosition;
		D3DXMatrixLookAtLH( &PPSLightView, &OrthoLightPosition, &OrthoLightTarget, &OrthoLightUp );

		D3DXVECTOR3 MinV, MaxV;

		TransformBoundingBox( PPSLightView, _A, _B, MinV, MaxV);

		MinV.z -= 1.0f;		// HACK!
		D3DXMatrixOrthoOffCenterLH( &PPSLightProj, MinV.x, MaxV.x, MinV.y, MaxV.y, MinV.z, MaxV.z );

		InvertDepthFunc = false;
	}
	else
	{
		InvertDepthFunc = PPSLight4.w > 0;

		PPSLight4 /= PPSLight4.w;
		D3DXVECTOR3 PPSLight((FLOAT*)PPSLight4);

		// Now the light source at PPSLight will look at a cube [-1,-1,0] [1,1,1]
		// We'll get a bounding cone by looking at the center [0,0,0.5]
		D3DXVECTOR3 PPSLightDirection = D3DXVECTOR3(0,0,0.5f) - PPSLight;
		D3DXVec3Normalize( &PPSLightDirection, &PPSLightDirection );

		// Find the up direction
		D3DXVECTOR3 PPSLightUp = GetUpDirection( PPSLightDirection );

		vector<AABB> aabb2;
		VEC3 _a(_A);
		VEC3 _b(_B);
		VEC3 ppslight(PPSLight);
		VEC3 ppslightdirection(PPSLightDirection);
		VEC3 ppslightup(PPSLightUp);

		aabb2.push_back( AABB(_a, _b) );
		Frustum LightFrustum( ppslight, ppslightdirection, ppslightup, aabb2 );

		// OK, now we can get LightView and LightProj in PPS
		D3DXVECTOR3 sum = PPSLightDirection + PPSLight;
		D3DXMatrixLookAtLH( &PPSLightView, &PPSLight, &sum, &PPSLightUp );
		// HACK!
		if (InvertDepthFunc)
			LightFrustum.farclip *= 1.1f;
		else
			LightFrustum.nearclip *= 0.9f;
		D3DXMatrixPerspectiveLH( &PPSLightProj, 
			LightFrustum.nearclip * tanf(LightFrustum.fovx * 0.5f) * 2.0f,
			LightFrustum.nearclip * tanf(LightFrustum.fovy * 0.5f) * 2.0f,
			LightFrustum.nearclip, LightFrustum.farclip );
	}

	ShadowGen = World2CameraPPS * PPSLightView * PPSLightProj;
	ShadowUse = ShadowGen * CubeToTexture;

	// We store the negative Z value in the shadow map texture if Z testing is reverted
	if (InvertDepthFunc)
	{
		D3DXMATRIXA16 InvertZ;

		D3DXMatrixScaling( &InvertZ, 1.0f, 1.0f, -1.0f );
		ShadowUse = ShadowUse * InvertZ;
	}
}

//
//void ShadowSolution::UpdateTransform_LSPSM()
//{
//	InvertDepthFunc = false;
//
//	// Find the tight frustum from eye
//	Frustum TightFrustum = GetTightEyeFrustum();
//
//	if (ToCreateDebugInfo)
//		DebugFrustum.push_back(TightFrustum);
//
//	D3DXVECTOR3	VirtualUp = -LightDirection;
//	D3DXVECTOR3 VirtualRight;
//	D3DXVECTOR3 VirtualView;
//
//	if (D3DXVec3Dot( &LightDirection, &CameraDirection ) > 0.99f)
//	{
//		// Light and view directions are (almost) identical, randomly pick a right vector
//		VirtualRight = GetUpDirection( VirtualUp );
//		D3DXVec3Cross( &VirtualView, &VirtualUp, &VirtualRight );
//	}
//	else
//	{
//		D3DXVec3Cross( &VirtualRight, &LightDirection, &CameraDirection );
//		D3DXVec3Normalize( &VirtualRight, &VirtualRight );
//
//		D3DXVec3Cross( &VirtualView, &VirtualUp, &VirtualRight );
//		if (D3DXVec3Dot( &VirtualView, &CameraDirection ) < 0)
//		{
//			VirtualRight = -VirtualRight;
//			VirtualView = -VirtualView;
//		}
//	}
//
//	// First use frustum center as the virtual camera position
//	D3DXVECTOR3	VirtualCameraPosition = CameraPosition + CameraDirection * 0.5f * (TightFrustum.nearclip + TightFrustum.farclip);
//	D3DXMATRIXA16 VirtualCameraView, VirtualCameraProj;
//
//	D3DXMatrixLookAtLH( &VirtualCameraView, &VirtualCameraPosition, &(VirtualCameraPosition + VirtualView), &VirtualUp );
//
//	float _Near = FLT_MAX;
//	float _Far = -FLT_MAX;
//	for (int i = 0; i < 8; i++)
//	{
//		D3DXVECTOR3 TransformedV;
//
//		D3DXVec3TransformCoord( &TransformedV, &D3DXVECTOR3( &TightFrustum.vert[i] ), &VirtualCameraView );
//		_Near = min(_Near, TransformedV.z);
//		_Far = max(_Far, TransformedV.z);		
//	}
//
//	float shift = (FarOverNear * _Near - _Far) / (FarOverNear - 1.0f);
//	VirtualCameraPosition += VirtualView * shift;
//	D3DXMatrixLookAtLH( &VirtualCameraView, &VirtualCameraPosition, &(VirtualCameraPosition + VirtualView), &VirtualUp );
//
//	Frustum VirtualCameraFrustum( VEC3(VirtualCameraPosition), VEC3(VirtualView), VEC3(VirtualUp), TightFrustum );
//	D3DXMatrixPerspectiveFovLH( &VirtualCameraProj, 
//		VirtualCameraFrustum.fovy, 
//		VirtualCameraFrustum.aspect, 
//		VirtualCameraFrustum.nearclip, 
//		VirtualCameraFrustum.farclip );
//
//	if (ToCreateDebugInfo)
//	{
//		Frustum DebugVirtualFrustum( VEC3(VirtualCameraPosition), VEC3(VirtualView), VEC3(VirtualUp),
//			VirtualCameraFrustum.nearclip, 
//			VirtualCameraFrustum.farclip, 
//			VirtualCameraFrustum.fovx, 
//			VirtualCameraFrustum.fovy);
//
//		DebugFrustum.push_back(DebugVirtualFrustum);
//	}
//
//	// Now we're in the post-perspective space, and view direction is [0,-1,0]
//	D3DXVECTOR3	PPSLightDirection(0, -1, 0);
//	D3DXVECTOR3	PPSUpDirection = GetUpDirection( PPSLightDirection );
//	D3DXVECTOR3 PPSLightPosition(0, 0, 0.5f);
//	D3DXVECTOR3 NewMinV, NewMaxV;
//
//	D3DXMATRIXA16 LightView, LightProj;
//	D3DXMatrixLookAtLH( &LightView, &PPSLightPosition, &(PPSLightPosition + PPSLightDirection), &PPSUpDirection );
//
//	TransformBoundingBox( LightView, D3DXVECTOR3(-1,-1,0), D3DXVECTOR3(1,1,1), NewMinV, NewMaxV );
//
//	// We'll not only look at [-1,-1,0]-[1,1,1], but also take scene bounding box into consideration
//	D3DXMATRIXA16 TempMat = VirtualCameraView * VirtualCameraProj * LightView;
//
//	// Scene bounding box - only the Z value
//	for (int i =0; i < 8; i++)
//	{
//		D3DXVECTOR4 Vert(&g_scene.aabb.Vertex(i));
//		Vert.w = 1.0f;
//
//		D3DXVECTOR4 TransformedV;
//
//		D3DXVec4Transform( &TransformedV, &Vert, &TempMat );
//		TransformedV *= 1.0f / TransformedV.w;
//
//		NewMinV.z = min(NewMinV.z, TransformedV.z);
//	}
//
//	NewMinV.z = max(NewMinV.z, -5.0f);	// Hack!
//
//	VEC3 SafeBoundary = (NewMaxV - NewMinV) * 0.1f; // 10%
//	D3DXMatrixOrthoOffCenterLH( &LightProj, 
//		NewMinV.x - SafeBoundary.x, 
//		NewMaxV.x + SafeBoundary.x, 
//		NewMinV.y - SafeBoundary.y, 
//		NewMaxV.y + SafeBoundary.y, 
//		NewMinV.z - SafeBoundary.z, 
//		NewMaxV.z + SafeBoundary.z );
//
//	D3DXMATRIXA16	TempMat0 = VirtualCameraView * VirtualCameraProj;
//	D3DXMATRIXA16	TempMat1 = LightView * LightProj;
//	ShadowGen = VirtualCameraView * VirtualCameraProj * LightView * LightProj;
//	ShadowUse = ShadowGen * CubeToTexture;	
//}
//
//void ShadowSolution::UpdateTransform_LSPSM_OPT()
//{
//	InvertDepthFunc = false;
//
//	float FieldOfViewX = 2.0f * atanf(Aspect * tanf(FieldOfViewY * 0.5f));
//
//	Frustum EyeFrustum(VEC3(CameraPosition), VEC3(CameraDirection), VEC3(CameraUp), NearClip, FarClip, FieldOfViewX, FieldOfViewY); 
//
//	FConvexHull EyeFrustumHull(EyeFrustum, true);
//	FConvexHull AABBHull(g_scene.aabb);
//	FConvexHull IntersectionHull;
//
//	AABBHull.IntersectByConvexHull(IntersectionHull, EyeFrustumHull);
//
//	VEC3 HullVertices[CONVEX_MAX_PLANE * CONVEX_MAX_VERTEX];
//	int nVertex = IntersectionHull.OutputVertex(HullVertices, false);
//
//	if (ToCreateDebugInfo)
//	{
//		for (int i = 0; i < nVertex; i++)
//			DebugVertex.push_back(HullVertices[i]);
//	}
//
//	D3DXVECTOR3	VirtualUp = -LightDirection;
//	D3DXVECTOR3 VirtualRight;
//	D3DXVECTOR3 VirtualView;
//
//	if (D3DXVec3Dot( &LightDirection, &CameraDirection ) > 0.99f)
//	{
//		// Light and view directions are (almost) identical, randomly pick a right vector
//		VirtualRight = GetUpDirection( VirtualUp );
//		D3DXVec3Cross( &VirtualView, &VirtualUp, &VirtualRight );
//	}
//	else
//	{
//		D3DXVec3Cross( &VirtualRight, &LightDirection, &CameraDirection );
//		D3DXVec3Normalize( &VirtualRight, &VirtualRight );
//
//		D3DXVec3Cross( &VirtualView, &VirtualUp, &VirtualRight );
//		if (D3DXVec3Dot( &VirtualView, &CameraDirection ) < 0)
//		{
//			VirtualRight = -VirtualRight;
//			VirtualView = -VirtualView;
//		}
//	}
//
//	// First use the interested vertices' center as the virtual camera position;
//	D3DXVECTOR3	VirtualCameraPosition(0,0,0);
//	//int N = HullVertices.size();
//	for (int i = 0; i < nVertex; i++)
//	{
//		VirtualCameraPosition += D3DXVECTOR3((float*)&HullVertices[i]);
//	}
//	if (nVertex > 0)
//		VirtualCameraPosition *= 1.0f / nVertex;
//
//	// First use frustum center as the virtual camera position
//	D3DXMATRIXA16 VirtualCameraView, VirtualCameraProj;
//
//	D3DXMatrixLookAtLH( &VirtualCameraView, &VirtualCameraPosition, &(VirtualCameraPosition + VirtualView), &VirtualUp );
//
//	float _Near = FLT_MAX;
//	float _Far = -FLT_MAX;
//	for (int i = 0; i < nVertex; i++)
//	{
//		D3DXVECTOR3 TransformedV;
//
//		D3DXVec3TransformCoord( &TransformedV, &D3DXVECTOR3(&HullVertices[i]), &VirtualCameraView );
//		_Near = min(_Near, TransformedV.z);
//		_Far = max(_Far, TransformedV.z);		
//	}
//
//	float shift = (FarOverNear * _Near - _Far) / (FarOverNear - 1.0f);
//	VirtualCameraPosition += VirtualView * shift;
//	D3DXMatrixLookAtLH( &VirtualCameraView, &VirtualCameraPosition, &(VirtualCameraPosition + VirtualView), &VirtualUp );
//
//	Frustum VirtualCameraFrustum( VEC3(VirtualCameraPosition), VEC3(VirtualView), VEC3(VirtualUp), HullVertices, nVertex );
//	D3DXMatrixPerspectiveFovLH( &VirtualCameraProj, 
//		VirtualCameraFrustum.fovy, 
//		VirtualCameraFrustum.aspect, 
//		VirtualCameraFrustum.nearclip, 
//		VirtualCameraFrustum.farclip );
//
//	if (ToCreateDebugInfo)
//	{
//		DebugFrustum.push_back(VirtualCameraFrustum);
//	}
//
//	// Now we're in the post-perspective space, and view direction is [0,-1,0]
//	D3DXVECTOR3	PPSLightDirection(0, -1, 0);
//	D3DXVECTOR3	PPSUpDirection = GetUpDirection( PPSLightDirection );
//	D3DXVECTOR3 PPSLightPosition(0, 0, 0.5f);
//	D3DXVECTOR3 NewMinV, NewMaxV;
//
//	D3DXMATRIXA16 LightView, LightProj;
//	D3DXMatrixLookAtLH( &LightView, &PPSLightPosition, &(PPSLightPosition + PPSLightDirection), &PPSUpDirection );
//
//	TransformBoundingBox( LightView, D3DXVECTOR3(-1,-1,0), D3DXVECTOR3(1,1,1), NewMinV, NewMaxV );
//
//	// We'll not only look at [-1,-1,0]-[1,1,1], but also take scene bounding box into consideration
//	D3DXMATRIXA16 TempMat = VirtualCameraView * VirtualCameraProj * LightView;
//
//	// B ^ Frustum_without_upper_plane
//	FConvexHull OpenFrustumHull(VirtualCameraFrustum, false);
//	FConvexHull FullShadowMapHull;
//
//	AABBHull.IntersectByConvexHull(FullShadowMapHull, OpenFrustumHull);
//
//	VEC3 FullHullVertices[CONVEX_MAX_PLANE * CONVEX_MAX_VERTEX];
//	nVertex = FullShadowMapHull.OutputVertex(FullHullVertices, false);
//	for (int i =0; i < nVertex; i++)
//	{
//		D3DXVECTOR4 Vert(&FullHullVertices[i]);
//		Vert.w = 1.0f;
//
//		D3DXVECTOR4 TransformedV;
//
//		D3DXVec4Transform( &TransformedV, &Vert, &TempMat );
//		TransformedV *= 1.0f / TransformedV.w;
//
//		NewMinV.z = min(NewMinV.z, TransformedV.z);
//	}
//
//	D3DXMatrixOrthoOffCenterLH( &LightProj, NewMinV.x, NewMaxV.x, NewMinV.y, NewMaxV.y, NewMinV.z, NewMaxV.z );
//
//	ShadowGen = VirtualCameraView * VirtualCameraProj * LightView * LightProj;
//	ShadowUse = ShadowGen * CubeToTexture;	
//}

// Use PCA to apply an translation+rotation transformation on final image plane to maximum the shadow map usage
// http://www.farinhansford.com/books/pla/material/PCA.pdf
// All the operations are performed on plane (Y = 0)
// Finally the vitual camera will look down to -Y, the initial bounding box is [-1,-1,0] - [1,1,1]
void PointSet_PCA(VEC3& Center, VEC3& Up, float& l, float& r, float& b, float& t, VEC3 Verts[], int nVertex)
{
	//int N = Verts.size();
	if (nVertex < 2)
		return;

	VEC3 Buffer[CONVEX_MAX_PLANE * CONVEX_MAX_VERTEX];
	memcpy(Buffer, Verts, sizeof(VEC3) * nVertex);

	Center = VEC3(0,0,0);
	for (int i = 0; i < nVertex; i++)
	{
		Buffer[i].y = 0;
		Center += Buffer[i];
	}
	Center *= 1.0f / nVertex;
	
	for (int i = 0; i < nVertex; i++)
	{
		Buffer[i] = Buffer[i] - Center;
	}

	// Mat = 
	//		  | A  C |
	//		  | C  B |
	//
	float A = 0, B = 0, C = 0;
	for (int i = 0; i < nVertex; i++)
	{
		A += Buffer[i].x * Buffer[i].x;
		B += Buffer[i].z * Buffer[i].z;
		C += Buffer[i].x * Buffer[i].z;		
	}
	
	float tmp = sqrtf( (A - B) * (A - B) + 4.0f * C * C );
	float lamda0 = 0.5f * (A + B + tmp);
	float lamda1 = 0.5f * (A + B - tmp);	// lamda0 >= lamda1
	
	VEC3 EigenV0, EigenV1;

	float C2 = C * C;
	tmp = (B - lamda0) * (B - lamda0);
	EigenV0.z = sqrtf(C2 / (C2 + tmp));
	EigenV0.x = sqrtf(1.0f - EigenV0.z * EigenV0.z);
	EigenV0.y = 0;

	tmp = (A - lamda1) * (A - lamda1);
	EigenV1.z = -sqrtf(tmp / (tmp + C2));
	EigenV1.x = sqrtf(1.0f - EigenV1.z * EigenV1.z);
	EigenV1.y = 0;

	Up = EigenV1;	// The short axis

	l = FLT_MAX;
	r = -FLT_MAX;
	b = FLT_MAX;
	t = -FLT_MAX;
	for (int i = 0; i < nVertex; i++)
	{
		float Long = Buffer[i].Dot(EigenV0);
		float Short = Buffer[i].Dot(EigenV1);

		l = min(l, Long);
		r = max(r, Long);
		b = min(b, Short);
		t = max(t, Short);
	}

	float Efficiency = 1.0f;
	Efficiency = (r - l) * (t - b) / 2.0f;

	if (Efficiency > 0.96f)
	{
		// revert to default setting
		Center = VEC3(0, 0, 0.5f);
		Up = VEC3(1, 0, 0);
		l = -0.5f;
		r = 0.5f;
		b = -1.0f;
		t = 1.0f;
	}
}

//void ShadowSolution::UpdateTransform_LSPSM_OPT_PCA()
//{	
//	InvertDepthFunc = false;
//
//	float FieldOfViewX = 2.0f * atanf(Aspect * tanf(FieldOfViewY * 0.5f));
//
//	Frustum EyeFrustum(VEC3(CameraPosition), VEC3(CameraDirection), VEC3(CameraUp), NearClip, FarClip, FieldOfViewX, FieldOfViewY); 
//
//	FConvexHull EyeFrustumHull(EyeFrustum, true);
//	FConvexHull AABBHull(g_scene.aabb);
//	FConvexHull IntersectionHull;	
//	AABBHull.IntersectByConvexHull(IntersectionHull, EyeFrustumHull);
//
//	VEC3 HullVertices[CONVEX_MAX_VERTEX * CONVEX_MAX_PLANE];
//	int nVertex = IntersectionHull.OutputVertex(HullVertices, false);
//
//	D3DXVECTOR3	VirtualUp = -LightDirection;
//	D3DXVECTOR3 VirtualRight;
//	D3DXVECTOR3 VirtualView;
//
//	if (D3DXVec3Dot( &LightDirection, &CameraDirection ) > 0.99f)
//	{
//		// Light and view directions are (almost) identical, randomly pick a right vector
//		VirtualRight = GetUpDirection( VirtualUp );
//		D3DXVec3Cross( &VirtualView, &VirtualUp, &VirtualRight );
//	}
//	else
//	{
//		D3DXVec3Cross( &VirtualRight, &LightDirection, &CameraDirection );
//		D3DXVec3Normalize( &VirtualRight, &VirtualRight );
//
//		D3DXVec3Cross( &VirtualView, &VirtualUp, &VirtualRight );
//		if (D3DXVec3Dot( &VirtualView, &CameraDirection ) < 0)
//		{
//			VirtualRight = -VirtualRight;
//			VirtualView = -VirtualView;
//		}
//	}
//
//	// First use the interested vertices' center as the virtual camera position;
//	D3DXVECTOR3	VirtualCameraPosition(0,0,0);
//	//int N = HullVertices.size();
//	for (int i = 0; i < nVertex; i++)
//	{
//		VirtualCameraPosition += D3DXVECTOR3((float*)&HullVertices[i]);
//	}
//	if (nVertex > 0)
//		VirtualCameraPosition *= 1.0f / nVertex;
//
//	// First use frustum center as the virtual camera position
//	D3DXMATRIXA16 VirtualCameraView, VirtualCameraProj;
//
//	D3DXMatrixLookAtLH( &VirtualCameraView, &VirtualCameraPosition, &(VirtualCameraPosition + VirtualView), &VirtualUp );
//
//	float _Near = FLT_MAX;
//	float _Far = -FLT_MAX;
//	for (int i = 0; i < nVertex; i++)
//	{
//		D3DXVECTOR3 TransformedV;
//
//		D3DXVec3TransformCoord( &TransformedV, &D3DXVECTOR3(&HullVertices[i]), &VirtualCameraView );
//		_Near = min(_Near, TransformedV.z);
//		_Far = max(_Far, TransformedV.z);		
//	}
//
//	float shift = (FarOverNear * _Near - _Far) / (FarOverNear - 1.0f);
//	VirtualCameraPosition += VirtualView * shift;
//	D3DXMatrixLookAtLH( &VirtualCameraView, &VirtualCameraPosition, &(VirtualCameraPosition + VirtualView), &VirtualUp );
//
//	Frustum VirtualCameraFrustum( VEC3(VirtualCameraPosition), VEC3(VirtualView), VEC3(VirtualUp), HullVertices, nVertex );
//	D3DXMatrixPerspectiveFovLH( &VirtualCameraProj, 
//		VirtualCameraFrustum.fovy, 
//		VirtualCameraFrustum.aspect, 
//		VirtualCameraFrustum.nearclip, 
//		VirtualCameraFrustum.farclip );
//
//	if (ToCreateDebugInfo)
//	{
//		DebugFrustum.push_back(VirtualCameraFrustum);
//	}
//
//	// Now we're in the post-perspective space, and view direction is [0,-1,0]
//	D3DXVECTOR3	PPSLightDirection(0, -1, 0);
//	D3DXVECTOR3	PPSUpDirection;
//	D3DXVECTOR3 PPSLightPosition;
//	float MinY = FLT_MAX;
//	float MaxY = -FLT_MAX;
//
//	// The unit cube should be [-1,-1,0] - [1,1,1], but (B ^ Frustum_without_upper_plane)'s Y value may > 1.0
//	// B ^ Frustum_without_upper_plane
//	FConvexHull OpenFrustumHull(VirtualCameraFrustum, false);
//	FConvexHull FullShadowMapHull;	
//	AABBHull.IntersectByConvexHull(FullShadowMapHull, OpenFrustumHull);
//
//	VEC3 FullHullVertices[CONVEX_MAX_PLANE * CONVEX_MAX_VERTEX];
//	nVertex = FullShadowMapHull.OutputVertex(FullHullVertices, false);
//
//	if (ToCreateDebugInfo)
//	{
//		for (int i = 0; i < nVertex; i++)
//			DebugVertex.push_back(FullHullVertices[i]);
//	}
//
//	// Transform the vertices of FullShadowMapHull into PPS space
//	D3DXMATRIXA16 TempMat = VirtualCameraView * VirtualCameraProj;
//	VEC3 PPSVerts[CONVEX_MAX_PLANE * CONVEX_MAX_VERTEX];
//	for (int i =0; i < nVertex; i++)
//	{
//		D3DXVECTOR4 Vert(&FullHullVertices[i]);
//		Vert.w = 1.0f;
//
//		D3DXVECTOR4 TransformedV;
//
//		D3DXVec4Transform( &TransformedV, &Vert, &TempMat );
//		TransformedV *= 1.0f / TransformedV.w;
//
//		MaxY = max(MaxY, TransformedV.y);
//		MinY = min(MinY, TransformedV.y);
//
//		PPSVerts[i] = VEC3(TransformedV.x, TransformedV.y, TransformedV.z);
//	}
//
//	if (ToCreateDebugInfo)
//	{
//		for (int i = 0; i < nVertex; i++)
//		{
//			DebugVertex2D.push_back(VEC2(PPSVerts[i].x * 0.5f + 0.5f, PPSVerts[i].z));
//		}
//	}
//
//	VEC3 CenterP, UpDir;
//	float l, r, b, t;
//	PointSet_PCA(CenterP, UpDir, l, r, b, t, PPSVerts, nVertex);	// Operation on plane Y = 0
//
//	D3DXMATRIXA16 LightView, LightProj;
//	PPSLightPosition = D3DXVECTOR3(CenterP.x, 0, CenterP.z);
//	PPSUpDirection = D3DXVECTOR3(&UpDir);
//
//	D3DXMatrixLookAtLH( &LightView, &PPSLightPosition, &(PPSLightPosition + PPSLightDirection), &PPSUpDirection );
//	D3DXMatrixOrthoOffCenterLH( &LightProj, l, r, b, t, -MaxY, -MinY );//NewMinV.x, NewMaxV.x, NewMinV.y, NewMaxV.y, NewMinV.z, NewMaxV.z );
//
//	ShadowGen = VirtualCameraView * VirtualCameraProj * LightView * LightProj /** ImagePCAMat*/;
//	ShadowUse = ShadowGen * CubeToTexture;	
//}
//
void ShadowSolution::CreateDebugFrustums()
{
	ToCreateDebugInfo = true;
	DebugFrustum.clear();
	DebugVertex.clear();
	DebugVertex2D.clear();
}

const Frustum* ShadowSolution::GetDebugFrustum(int FrustumID) const
{
	if (FrustumID < int(DebugFrustum.size()))
	{
		return &DebugFrustum[FrustumID];
	}
	else
	{
		return 0;
	}
}

const VEC3* ShadowSolution::GetDebugVertex(int VertexID) const
{
	if (VertexID < int(DebugVertex.size()))
	{
		return (VEC3*)(&(DebugVertex[VertexID]));
	}
	else
	{
		return 0;
	}
}

const VEC2* ShadowSolution::GetDebugVertex2D(int VertexID) const
{
	if (VertexID < int(DebugVertex2D.size()))
	{
		return (VEC2*)(&(DebugVertex2D[VertexID]));
	}
	else
	{
		return 0;
	}
}